﻿using Axiom.CodeManifold.Framework;
using Axiom.CodeManifold.Properties;
using EnvDTE;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Axiom.CodeManifold
{
	internal static class SortExtension
	{
		public static void Sort(this CodeType entity)
		{
			var elements = new List<MemberContainer>();
			foreach (CodeElement member in entity.Members)
			{
				elements.Add(new MemberContainer(member));

				entity.RemoveMember(member);
			}

			elements.Sort();

			var edit = WriteMembers(entity, elements);

			foreach (CodeElement member in entity.Members)
			{
				if (member.Kind == vsCMElement.vsCMElementFunction)
				{
					(member as CodeFunction).DocComment = elements.Where(a => a.Signature == (member as CodeFunction).GetSignature()).First().DocComment;
				}
				else if (member.Kind == vsCMElement.vsCMElementProperty)
				{
					(member as CodeProperty).DocComment = elements.Where(a => a.Name == member.Name).First().DocComment;
				}
				else if (member.Kind == vsCMElement.vsCMElementVariable)
				{
					(member as CodeVariable).Comment = elements.Where(a => a.Name == member.Name).First().Comment;
					(member as CodeVariable).DocComment = elements.Where(a => a.Name == member.Name).First().DocComment;
				}
			}

			edit.Indent(null, 1);
		}

		public static void SortMembers(this Document doc)
		{
			if (doc == null || doc.ProjectItem == null || doc.ProjectItem.FileCodeModel == null)
			{
				throw new NoActiveDocumentException("No Active Document");
			}

			var entities = doc.Entities<CodeType>();

			foreach (CodeType entity in entities)
			{
				entity.Sort();
			}
			doc.DTE.ExecuteCommand(@"Edit.FormatDocument", string.Empty);
		}

		private static string FriendlyName(vsCMElement vsCMElement)
		{
			switch (vsCMElement)
			{
				case vsCMElement.vsCMElementVariable:
					return "Fields";
				case vsCMElement.vsCMElementProperty:
					return "Properties";
				case vsCMElement.vsCMElementEvent:
					return "Events";
				case vsCMElement.vsCMElementFunction:
					return "Functions";
				default:
					return "Other";
			}
		}

		private static void WriteElements(List<MemberContainer> elements, EditPoint edit)
		{
			if (ReferenceData.UseRegions && elements.Count > ReferenceData.RegionMinSize)
			{
				edit.Insert(string.Format("\t#region {0}" + "\r\n", FriendlyName(elements.First().ElementKind)));
			}

			elements.ForEach(a => edit.Insert("\t\t" + a.FullText + "\r\n\r\n"));

			if (ReferenceData.UseRegions && elements.Count > ReferenceData.RegionMinSize)
			{
				edit.Insert(string.Format("\t#endregion {0}" + "\r\n\r\n", FriendlyName(elements.First().ElementKind)));
			}
		}

		private static EditPoint WriteMembers(CodeType entity, List<MemberContainer> elements)
		{
			var start = entity.GetStartPoint(vsCMPart.vsCMPartBody);
			var edit = start.CreateEditPoint();

			WriteElements(elements.Where(a => a.ElementKind == vsCMElement.vsCMElementVariable).ToList(), edit);
			WriteElements(elements.Where(a => a.ElementKind == vsCMElement.vsCMElementProperty).ToList(), edit);
			WriteElements(elements.Where(a => a.ElementKind == vsCMElement.vsCMElementEvent).ToList(), edit);
			WriteElements(elements.Where(a => a.ElementKind == vsCMElement.vsCMElementFunction).ToList(), edit);

			return edit;
		}

	}

	internal class MemberContainer : IComparable<MemberContainer>
	{
		static List<vsCMElement> TypesWithBody = new List<vsCMElement>()
		{
			vsCMElement.vsCMElementFunction,
			vsCMElement.vsCMElementProperty,			
		};

		#region Properties
		public vsCMAccess Access { get; set; }

		public string Body { get; set; }

		public string Comment { get; set; }

		public string DocComment { get; set; }

		public vsCMElement ElementKind { get; set; }

		public string FullText { get; set; }

		public vsCMFunction FunctionKind { get; set; }

		public string Name { get; set; }

		public string Signature { get; set; }

		public string Type { get; set; }
		#endregion Properties

		#region Functions
		public MemberContainer(CodeElement element)
		{
			switch (element.Kind)
			{
				case vsCMElement.vsCMElementFunction:
					CodeFunction function = element as CodeFunction;
					this.DocComment = (element as CodeFunction).DocComment;
					this.Comment = (element as CodeFunction).Comment;
					this.Body = (element as CodeFunction).GetBody();
					this.Access = (element as CodeFunction).Access;
					this.FunctionKind = function.FunctionKind;
					this.Signature = function.GetSignature();

					this.Type = function.Type.GetTypeAsString();
					break;
				case vsCMElement.vsCMElementProperty:
					this.DocComment = (element as CodeProperty).DocComment;
					this.Comment = (element as CodeProperty).Comment;
					this.Access = (element as CodeProperty).Access;
					break;
				case vsCMElement.vsCMElementVariable:
					this.DocComment = (element as CodeVariable).DocComment;
					this.Comment = (element as CodeVariable).Comment;
					this.Access = (element as CodeVariable).Access;
					break;
				default:
					break;
			}
			var part = vsCMPart.vsCMPartWholeWithAttributes;
			var start = element.GetStartPoint(part);
			this.FullText = element.GetEndPoint(part).CreateEditPoint().GetText(start);
			this.ElementKind = element.Kind;
			this.Name = element.Name;
		}

		public int CompareTo(MemberContainer other)
		{
			int result = 0;

			if (this.FunctionKind == vsCMFunction.vsCMFunctionConstructor)
			{
				return -5;
			}

			if (other.FunctionKind == vsCMFunction.vsCMFunctionConstructor)
			{
				return 5;
			}

			result = Order(this.ElementKind).CompareTo(Order(other.ElementKind));
			if (result != 0)
			{
				return result;
			}

			result = Order(this.FunctionKind).CompareTo(Order(other.FunctionKind));
			if (result != 0)
			{
				return result;
			}

			result = Order(this.Access).CompareTo(Order(other.Access));
			if (result != 0)
			{
				return result;
			}

			result = this.Name.CompareTo(other.Name);
			if (result != 0)
			{
				return result;
			}

			return result;
		}

		public int Order(vsCMElement kind)
		{
			switch (kind)
			{
				case vsCMElement.vsCMElementIncludeStmt:
				case vsCMElement.vsCMElementImportStmt:
					return -4;
				case vsCMElement.vsCMElementVariable:
					return -3;
				case vsCMElement.vsCMElementProperty:
					return -2;
				case vsCMElement.vsCMElementEvent:
					return -1;
				case vsCMElement.vsCMElementFunction:
					return 1;
				default:
					return 0;
			}
		}

		public int Order(vsCMAccess kind)
		{
			switch (kind)
			{
				case vsCMAccess.vsCMAccessDefault:
				case vsCMAccess.vsCMAccessPublic:
					return -2;
				case vsCMAccess.vsCMAccessProtected:
					return -1;
				case vsCMAccess.vsCMAccessPrivate:
					return 1;
				default:
					return 0;
			}
		}

		public int Order(vsCMFunction kind)
		{
			switch (kind)
			{
				case vsCMFunction.vsCMFunctionConstructor:
					return -3;
				case vsCMFunction.vsCMFunctionDestructor:
					return -2;
				case vsCMFunction.vsCMFunctionFunction:
					return 1;
				case vsCMFunction.vsCMFunctionShared:
					return 2;
				default:
					return 0;
			}
		}

		public override string ToString()
		{
			return string.Format("{0} - {1}, {2}, {3}, {4}", Name, Access, ElementKind, FunctionKind, Signature);
		}
		#endregion Functions
	}
}